import simplejson as json
import tempfile

from sprite import *
from spriteeditor import *
from maptile import *
from maplayer import *
from main import *

class Map(QWidget):
    attrs = ["tile_id_map", "width", "height", "layers"] # tile_set, sprite_set
    def __init__(self, width, height, map_editor):
        QWidget.__init__(self, None)

        self.zoomlevel = 2

        self.dirty = True

        self.tile_cache = {}

        self.set_size(width, height)

        self.map_editor = map_editor

        self.sprite_editor = None
        self.erasing = False
        self.painting = False
        self.clear()

        self.timer = QTimer()
        self.connect(self.timer, SIGNAL("timeout()"), self.tick)
        #self.timer.start(50)

        # init default map

        self.add_layer(32,32)

        self.create_grid()

        self.buffer = None

    def tick(self):
        for layer in self.layers:
            for tile in layer.tiles:
                tile.advance_frame()
        self.update()

    def add_blank_tile(self):
        sprite = Sprite(self.layers[self.selected_layer].tile_width, self.layers[self.selected_layer].tile_height)
        sprite.add_empty_frame()
        self.tile_set.append(sprite)

    def add_tile(self, path):
        """Add a tile to the Map's tileset. Can be called from MapTileList."""

        sf = SpriteFile(None)
        sprite = sf.load(path)
        self.tile_set.append(sprite)

    def add_tile_from_sprite(self, sprite):
        """Add a tile to the Map's tileset. Can be called from MapTileList."""

        self.tile_set.append(sprite)

    def del_tile(self, index):
        """Remove a tile from the Map's tileset. Can be called from MapTileList."""
        tile = self.tile_set[index]
        self.tile_set.remove(tile)

    def add_layer(self, tile_width, tile_height):
        """Add a new layer to the Map with tile dimensions tile_width and tile_height."""
        l = MapLayer(tile_width, tile_height)
        self.layers.append(l)
        return l

    def set_size(self, width, height):
        self.width = width * self.zoomlevel
        self.height = height * self.zoomlevel
        self.setMinimumSize(self.width,self.height)
        self.setMaximumSize(self.width,self.height)
        self.resize(self.width,self.height)

    def set_selected_tile_id(self, tile_id):
        """Set tile id (to be placed)."""
        self.selected_tile_id = tile_id

    def set_selected_layer(self, selected_layer):
        """Set layer (to be drawn on)."""
        self.selected_layer = selected_layer
        self.create_grid()
        self.update()

    def open_sprite_editor(self, ev):
        # TODO: Get tile under cursor.
        if not self.sprite_editor:
            self.sprite_editor = MainWindowSprite(self.tile_set[self.selected_tile_id])
            self.connect(self.sprite_editor.sprite_editor, SIGNAL("painted()"), self.on_sprite_editor_painted)
        else:
            self.sprite_editor.sprite_editor.set_sprite(self.tile_set[self.selected_tile_id])
            self.sprite_editor.preview.animation.set_sprite(self.sprite_editor.sprite_editor.sprite)
            self.sprite_editor.layers_dialog.initialize_list()
            self.sprite_editor.update_title(str(self.selected_tile_id))
        self.sprite_editor.show()
        self.sprite_editor.sprite_editor.sprite.dirty = True
        self.sprite_editor.sprite_editor.update()

    """
    def mouseDoubleClickEvent(self, ev):
        self.open_sprite_editor(ev)
    """

    def on_sprite_editor_painted(self):
        self.map_editor.maptilelist.initialize_list()
        if self.selected_tile_id in self.tile_cache:
            del self.tile_cache[self.selected_tile_id]
        self.update()

    def mouseReleaseEvent(self, ev):
        # continue timer
        #self.timer.start(50)
        self.erasing = False
        self.painting = False

    def mouseMoveEvent(self, ev):
        self.mousePressEvent(ev)

    def mousePressEvent(self, ev):
        # stop timer
        #self.timer.stop()
        x = ev.pos().x()
        y = ev.pos().y()

        tx = (x / self.layers[self.selected_layer].tile_width) / self.zoomlevel
        ty = (y / self.layers[self.selected_layer].tile_height) / self.zoomlevel

        if self.erasing or ev.button() == Qt.RightButton:
            self.erasing = True
            self.layers[self.selected_layer].remove_tile(tx, ty)
            self.dirty = True
        else:
            self.painting = True
            if len(self.tile_set) > 0:
                self.layers[self.selected_layer].add_tile(tx, ty, self.tile_set[self.selected_tile_id], self.selected_tile_id)
                self.dirty = True

        self.update()

    def paintEvent(self,ev):
        rect = ev.rect()

        self.buffer = QPixmap(self.rect().size())


        p = QPainter(self.buffer)


        if self.dirty:
            p.fillRect(rect, QBrush(QColor("white")))
            gridrect = QRect(0,0,self.grid.width()*self.zoomlevel, self.grid.height()*self.zoomlevel)
            p.drawPixmap(rect, self.grid, rect)

        for layer in self.layers:
            for tile_number, tile in enumerate(layer.tiles):
                if tile.tile_id not in self.tile_cache:
                    self.tile_cache[tile.tile_id] = tile.get_pixmap().scaled(layer.tile_width*self.zoomlevel, layer.tile_height*self.zoomlevel)

                tile_pixmap = self.tile_cache[tile.tile_id]

                tile_rect = QRect(tile.x*layer.tile_width*self.zoomlevel, tile.y*layer.tile_height*self.zoomlevel, tile_pixmap.width(), tile_pixmap.height())

                if tile_rect.intersects(rect):
                    p.drawPixmap(tile_rect, tile_pixmap)

        p.end()

        p = QPainter(self)
        p.drawPixmap(rect, self.buffer, rect)
        p.end()

    def create_grid(self):
        self.grid = QPixmap(self.width * self.zoomlevel, self.height * self.zoomlevel)
        self.grid.fill(QColor(0,0,0,0))
        p = QPainter(self.grid)

        if len(self.layers) > 0:
            p.setOpacity(0.05)
            # grid it up

            x = 0
            y = 0
            while x < self.width:
                x += self.layers[self.selected_layer].tile_width * self.zoomlevel
                p1 = QPoint(x, 0)
                p2 = QPoint(x, self.height)
                p.drawLine(p1,p2)

            while y < self.height:
                y += self.layers[self.selected_layer].tile_height * self.zoomlevel 
                p1 = QPoint(0, y)
                p2 = QPoint(self.width, y)
                p.drawLine(p1,p2)

    def todict(self):
        dict = {}
        for attr in Map.attrs:
            if attr == "layers":
                dict[attr] = []
                for layer in self.layers:
                    dict[attr].append(layer.todict())
            else:
                dict[attr] = getattr(self, attr, None)

        dict["tile_set"]   = range(len(self.tile_set))
        dict["sprite_set"] = range(len(self.sprite_set))

        return dict


    def save(self, savefile):
        # save filename
        if not str(savefile).endswith(".bmz"):
            savefile = savefile + ".bmz"

        # get json data

        data = self.todict()

        # create temporary directory

        temppath = tempfile.mkdtemp()

        # write sprites

        zip = zipfile.ZipFile(str(savefile), "w")

        for index,tile in enumerate(self.tile_set):
            filename = "%s/tile_%s.bsz" % (temppath,index)
            sf = SpriteFile(tile)
            sf.save(filename)
            zip.write(filename,"tiles/%d.bsz" % index)

        for index,sprite in enumerate(self.sprite_set):
            filename = "%s/sprite_%s.bsz" % (temppath,index)
            sf = SpriteFile(sprite)
            sf.save(filename)
            zip.write(filename,"sprites/%d.bsz" % index)

        jsontmp = tempfile.NamedTemporaryFile()
        json.dump(data, jsontmp)
        jsontmp.flush()
        zip.write(jsontmp.name,"brm.json")

        zip.close()

        print "wrote ", savefile

    def fromjson(self, j):
        dict = json.loads(j)

        for attr in Map.attrs:
            if attr != "layers":
                setattr(self, attr, dict[attr])
            elif attr == "layers":
                for layer in dict[attr]:
                    l = self.add_layer(layer["tile_width"], layer["tile_height"])
                    for lattr in MapLayer.attrs:
                        if lattr == "tiles":
                            for tile in layer[lattr]:
                                l.add_tile(tile["x"],tile["y"],self.tile_set[tile["tile_id"]],tile["tile_id"])
                        elif lattr == "sprites":
                            for sprite in layer[lattr]:
                                l.add_sprite(sprite["x"],sprite["y"],self.sprite_set[sprite["sprite_id"]],sprite["sprite_id"])
                        else:
                            setattr(l, lattr, layer[lattr]) 

    def load(self, loadfile):

        zip = zipfile.ZipFile(str(loadfile), "r") 

        if "brm.json" in zip.namelist():

            # clear existing data

            self.clear()
            
            # create temporary directory

            temppath = tempfile.mkdtemp()

            sf = SpriteFile(None)

            jsontmp = tempfile.NamedTemporaryFile()

            namelist = zip.namelist()

            for name in namelist:
                if name.startswith("tiles/"):
                    jsontmp.write(zip.read(name))
                    jsontmp.flush()
                    self.tile_set.append(sf.load(jsontmp.name))
                if name.startswith("sprites/"):
                    self.sprite_set.append(zip.read(name))
                    
            j = zip.read("brm.json")
            
            self.fromjson(j)
            self.map_editor.maplayerlist.initialize_list()
        else:
            print "Invalid file format jijiji!"

    def save_flat(self, filename):
        file = open(filename, 'wb')
        for layer in self.layers:
            lookup = {}
            flat = []

            for tile in layer.tiles:
                lookup["%d,%d" % (tile.x, tile.y)] = tile.tile_id

            for sprite in layer.sprites:
                lookup["%d,%d" % (sprite.x, sprite.y)] = sprite.sprite_id

            for y in range(self.height / layer.tile_height):
                for x in range(self.height / layer.tile_height):
                    key = "%d,%d" % (x, y)
                    if key in lookup:
                        flat.append(struct.pack("<h", lookup[key]))
                    else:
                        flat.append(struct.pack("<h", -1))

            flat = ''.join(flat)
            file.write(flat)

    def clear(self):
        self.layers = []
        self.tile_set = []
        self.sprite_set = []

        self.selected_layer = 0
        self.selected_tile_id = 0

        self.tile_id_map = []

